
using System;

namespace stefc.geo.lib
{
	
	public class Geographic 
	{
		private double AVG_EARTH_RADIUS = 6371.0d;
		
		private readonly double lat; // breite 
		private readonly double lon; // länge
		
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="lat">
		/// Breitengrad
		/// </param>
		/// <param name="lon">
		/// Längengrad
		/// </param>
		public Geographic(double lat, double lon)
		{
			this.lat=lat;
			this.lon=lon;
		}
		
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="s">
		/// GPS Koordinate im Format GPSW85
		/// </param>
		public Geographic(string s)
		{
			string[] parts = s.Split(' ');
			string _lat=parts[0];
			string _lon=parts[1];
			
			this.lat=ToDouble(_lat);
			this.lon=ToDouble(_lon);
		}

		private double ToDouble(string s)
		{
			int degree=0;
			int min=0;
			int sec=0;
			
			int p=s.IndexOf('°');
			if(p==-1)
			{
				degree=Convert.ToInt32(s);
			}
			else
			{
				degree=Convert.ToInt32(s.Substring(0,p));
				s=s.Substring(p+1);
				p=s.IndexOf('\'');
				if(p==-1)
				{
					min=Convert.ToInt32(s);
				}
				else
				{
					min=Convert.ToInt32(s.Substring(0,p));
					s=s.Substring(p+1);
				}
			}
			return (1.0/60.0) * min + degree;
		}
		
		/// <summary>
		/// Breitengrad
		/// </summary>
		public double Lat	
		{	
			get { return this.lat;} 
		}
		
		/// <summary>
		/// Längengrad
		/// </summary>
		public double Lon
		{
			get { return this.lon; }
		}
		
		private double GetDistance(double Φa, double λa, double Φb, double  λb)
		{
			bool isEast = λa < λb;
			
			double a = (Math.PI/2-(isEast ? Φb:Φa));
			double b = (Math.PI/2-(isEast ? Φa:Φb));
			
			double γ = Math.Abs(λb-λa);
			
			double c = Math.Acos(
			      Math.Cos(a)*Math.Cos(b)+
			      Math.Sin(a)*Math.Sin(b)*Math.Cos(γ));
			
			double kc = c * AVG_EARTH_RADIUS;
		
			return kc;
		}
		
		/// <summary>
		/// Ermittlung Entfernung zu einer anderen Koordinate
		/// </summary>
		/// <param name="to">
		/// Koordinate
		/// </param>
		/// <returns>
		/// Entfernung in km
		/// </returns>
		public double GetDistance(Geographic to)
		{
			if(to!=null)
			{
				double ς = this.GetDistance(
				  	MathUtil.ToRadiant(this.Lat),
					MathUtil.ToRadiant(this.Lon),
			 		MathUtil.ToRadiant(to.Lat),
			 		MathUtil.ToRadiant(to.Lon));
				
				return ς;
			}
			return 0.0;
		}
		
		/// <summary>
		/// Berechnung Kurswinkel zu einer anderen Koordinate
		/// </summary>
		/// <param name="to">
		/// Koordinate
		/// </param>
		/// <returns>
		/// Kurswinkel Radiant
		/// </returns>
		public double GetAngle(Geographic to)
		{
			if(to!=null)
			{
				bool isEast = this.Lon<to.Lon;
				
				double a = MathUtil.ToRadiant(90-(isEast ? to.Lat : this.Lat));
				double b = MathUtil.ToRadiant(90-(isEast ? this.Lat: to.Lat));
				
				double γ = MathUtil.ToRadiant(Math.Abs(to.Lon-this.Lon));
				
				double c = Math.Acos(
				      Math.Cos(a)*Math.Cos(b)+
				      Math.Sin(a)*Math.Sin(b)*Math.Cos(γ));
				
				double α = Math.Acos(
				      (Math.Cos(a)-Math.Cos(b)*Math.Cos(c))
				                     / 
				      (Math.Sin(c)*Math.Sin(b))
				                     );
				
				double β = Math.Acos(
				      (Math.Cos(b)-Math.Cos(c)*Math.Cos(a))
				                     / 
				      (Math.Sin(c)*Math.Sin(a))
				                     );
						       
				if(isEast)
					return α;
				else
					return (2*Math.PI) - β;					
			}
			return 0.0;
		}
		
		private const string FORMAT_LONG = "{0}°{1}′{2}″{3}";
		private const string FORMAT_SHORT = "{0}°{3}";
		
		public override string ToString ()
		{
			return String.Format("{0} {1}", 
				FormatDegree(this.Lat,'S','N',FORMAT_LONG),
				FormatDegree(this.Lon,'W','E',FORMAT_LONG));
		}
		
		public string ToShortString()
		{
			return String.Format("{0} {1}", 
				FormatDegree(this.Lat,'S','N',FORMAT_SHORT),
				FormatDegree(this.Lon,'W','E',FORMAT_SHORT));
			
		}
		
		private string FormatDegree(double x, char ch1, char ch2, string format)
		{
			char ch=x < 0.0 ? ch1:ch2;
			x = Math.Abs(x);
			int deg=(int)x;
			int min=(int)((x-Math.Truncate(x))*60);
			int sec=(int)((x*100-Math.Truncate(x*100))*60);
			
			return String.Format(format,deg,min,sec,ch);
		}
        							
	}
}
